#!/usr/bin/env python3

import argparse
import collections
import os

import common

class Component:
    '''
    Yes, we are re-inventing a crappy dependency resolution system.
    I can't believe it.

    The hard part is that we have optional dependencies as well...
    e.g. buildroot optionally depends on m5 to put m5 in the root filesystem,
    and buildroot optionally depends on qemu to build the qcow2 version
    of the image.
    '''
    def __init__(self, build_callback=None, dependencies=None):
        self.build_callback = build_callback
        if dependencies is None:
            self.dependencies = []
        else:
            self.dependencies = dependencies
    def build(self, arch, dry_run):
        if self.build_callback is not None:
            self.build_callback(arch)

def build_baremetal(arch, dry_run):
    common.run_cmd(['build-crosstool-ng'], arch)
    common.run_cmd(['build-baremetal'], arch)
    common.run_cmd(['build-baremetal', '--gem5'], arch)
    common.run_cmd(['build-baremetal', '--gem5', '--machine', 'RealViewPBX'], arch)

def run_cmd(cmd, arch):
    global args
    cmd_abs = cmd.copy()
    cmd_abs[0] = os.path.join(common.root_dir, cmd[0])
    cmd_abs.extend(['--arch', arch])
    if args.extra_args:
        cmd_abs.append(args.extra_args)
    common.run_cmd(cmd_abs, dry_run=args.dry_run)

name_to_component_map = {
    # Leaves without dependencies.
    'baremetal': Component(
        lambda arch: build_baremetal(arch),
    ),
    'buildroot': Component(
        lambda arch: run_cmd(['build-buildroot'], arch),
    ),
    'buildroot-gcc': Component(
        lambda arch: run_cmd(['build-buildroot'], arch),
    ),
    'copy-overlay': Component(
        lambda arch: run_cmd(['copy-overlay'], arch),
    ),
    'gem5': Component(
        lambda arch: run_cmd(['build-gem5'], arch),
    ),
    'linux': Component(
        lambda arch: run_cmd(['build-linux'], arch),
    ),
    'modules': Component(
        lambda arch: run_cmd(['build-modules'], arch),
    ),
    'm5': Component(
        lambda arch: run_cmd(['build-m5'], arch),
    ),
    'qemu': Component(
        lambda arch: run_cmd(['build-qemu'], arch),
    ),
    'userland': Component(
        lambda arch: run_cmd(['build-userland'], arch),
    ),

    # Dependency only nodes.
    'all-linux': Component(dependencies=[
        'qemu',
        'gem5-buildroot',
    ]),
    'gem5-buildroot': Component(dependencies=[
        'buildroot-gcc',
        'linux',
        'm5',
        'overlay',
        'gem5',
    ]),
    'overlay': Component(dependencies=[
        'copy-overlay',
        'modules',
        'userland',
        'buildroot',
    ]),
    'qemu-buildroot': Component(dependencies=[
        'qemu',
        'buildroot-gcc',
        'overlay',
        'linux',
    ]),
    'all': Component(dependencies=[
        'all-linux',
        'baremetal',
    ])
}
parser = argparse.ArgumentParser(
    description= '''\
Shallow helper to build everything, or a subset of everything conveniently.

Our build-* scripts don't build any dependencies to make iterative
development fast and more predictable.

While modifying a specific component however, you will likely want to just run the
individual build-* commands which:

* build no dependencies, and so are fast and predictable
* can take multiple options to custumize the build

Without any args, build only what is necessary for
https://github.com/cirosantilli/linux-kernel-module-cheat#qemu-buildroot-setup
for x86_64:

....
./%(prog)s
....

This is equivalent to:

....
./%(prog)s --arch x86_64 qemu-buildroot
....

If `--arch` is given, build just for the given archs:

....
./%(prog)s --arch arm --arch aarch64
....

This will build `qemu-buildroot` for arm and aarch64 only, but not `x86_64`.

Clean all Linux kernel builds:

....
./build --all-archs --extra-args=--clean buildroot
....
''',
    formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument('--all', default=False, action='store_true', help='''\
Build absolutely everything for all archs.
''')
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--all-archs', default=False, action='store_true', help='''\
Build the selected components for all archs.
''')
group.add_argument('--arch', choices=common.arch_choices, default=[], action='append', help='''\
Build the selected components for this arch. Select multiple arches by
passing this option multiple times. Default: [{}]
'''.format(common.default_arch))
parser.add_argument('--extra-args', default='', help='''\
Extra args to pass to all scripts.
'''
)
parser.add_argument('components', choices=list(name_to_component_map.keys()) + [[]], default=[], nargs='*', help='''\
Which components to build.
'''.format(common.default_arch))
common.add_dry_run_argument(parser)
args = parser.parse_args()
common.setup_dry_run_arguments(args)

# Decide archs.
if args.arch == []:
    if args.all or args.all_archs:
        archs = common.all_archs.copy()
    else:
        archs = set([common.default_arch])
else:
    archs = set()
    for arch in args.arch:
        if arch in common.arch_short_to_long_dict:
            arch = common.arch_short_to_long_dict[arch]
        archs.add(arch)

# Decide components.
components = args.components
if args.all:
    components = ['all']
elif components == []:
    components = ['qemu-buildroot']
selected_components = []
selected_component_name_set = set()
for component_name in components:
    todo = [component_name]
    while todo != []:
        current_name = todo.pop(0)
        if current_name not in selected_component_name_set:
            selected_component_name_set.add(current_name)
            component = name_to_component_map[current_name]
            selected_components.append(component)
            todo.extend(component.dependencies)

# Do the build.
for arch in archs:
    for component in selected_components:
        component.build(arch, args.dry_run)
